// Copyright (c) 2009-2010, Jeremy Brewer
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package galaxie500.io;

import galaxie500.exceptions.ValueError;
import galaxie500.io.ColumnReader;

import java.io.BufferedReader;
import java.util.HashMap;
import java.util.Map;

/**
 * Class for parsing configuration files
 * 
 * The configuration files used by Galaxie500 consist of key value pairs of the
 * form:
 * 
 * <pre>
 * [param1]  value1   # Optional comment
 * # Comment line
 * [param2]  value2
 * </pre>
 * 
 * Where the [param] strings give the names of options and the value strings
 * give their values. Note that string values may not contain spaces.
 * 
 * @author Jeremy Brewer
 */
public class ConfigFileParser {
  private Map<String, String> options = new HashMap<String, String>();

  /**
   * Parses options from the input reader or dies if any parse error occurs.
   * 
   * @param reader The reader to read the configuration from
   * @throws IOError If the underlying reader can't be read
   * @throws ValueError If the input configuration contains bad syntax
   */
  public ConfigFileParser(BufferedReader reader) {
    ColumnReader columnReader = new ColumnReader(reader, 2);
    try {
      parseOptions(columnReader);
    } finally {
      columnReader.close();
    }
  }

  /**
   * Returns the value of a boolean option.
   * 
   * @param option The name of the option to look up
   * @param defaultValue The value to return if not found
   * @return The value of the option or the default value if not found
   */
  public boolean readBoolean(String option, boolean defaultValue) {
    String value = options.get(option);
    if (value == null) return defaultValue;
    return Boolean.parseBoolean(value);
  }

  /**
   * Returns the value of an int option.
   * 
   * @param option The name of the option to look up
   * @param defaultValue The value to return if not found
   * @return The value of the option or the default value if not found
   */
  public int readInt(String option, int defaultValue) {
    String value = options.get(option);
    if (value == null) return defaultValue;
    return Integer.parseInt(value);
  }

  /**
   * Returns the value of a float option.
   * 
   * @param option The name of the option to look up
   * @param defaultValue The value to return if not found
   * @return The value of the option or the default value if not found
   */
  public float readFloat(String option, float defaultValue) {
    String value = options.get(option);
    if (value == null) return defaultValue;
    return Float.parseFloat(value);
  }

  /**
   * Returns the value of a double option.
   * 
   * @param option The name of the option to look up
   * @param defaultValue The value to return if not found
   * @return The value of the option or the default value if not found
   */
  public double readDouble(String option, double defaultValue) {
    String value = options.get(option);
    if (value == null) return defaultValue;
    return Double.parseDouble(value);
  }

  /**
   * Returns the value of a string option. Note that strings may not contain
   * spaces.
   * 
   * @param option The name of the option to look up
   * @param defaultValue The value to return if not found
   * @return The value of the option or the default value if not found
   */
  public String readString(String option, String defaultValue) {
    String value = options.get(option);
    if (value == null) return defaultValue;
    return value;
  }

  /**
   * Parses the options using the given column reader.
   * 
   * @param reader The reader for the input config file
   * @throws IOError If the underlying reader can't be read
   * @throws ValueError If the input configuration contains bad syntax
   */
  private void parseOptions(ColumnReader reader) {
    while (reader.readLine()) {
      if (reader.getNumColumns() != 2) {
        throw new ValueError("Expected 2 columns, but got " +
                             reader.getNumColumns() + ": " + reader.getLine());
      }
      String[] columns = reader.getColumns();
      String key = columns[0];
      String value = columns[1];
      if (key.startsWith("#")) continue; // Commented line.

      if (options.containsKey(key)) {
        throw new ValueError(
            "Input contains multiple instances of parameter '" + key + "'");
      }

      options.put(key, value);
    }
  }
}
